package emby.expr;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;

import emby.EmbyRuntime;
import emby.err.TypeError;
import emby.lang.EmbyList;
import emby.lang.EmbyObject;
import emby.lang.Scope;
import emby.overload.OverloadDot;
import emby.util.ReflectionHelp;
import emby.writer.IEmbyCodifier;

/**
 * The AST representation of a dot-operator call. Used for member access.
 * @author Justin
 */
public class DotVariable extends Variable {
	private static final long serialVersionUID = -462937842093788444L;
	public Expression arg1;
	public String arg2;
	public DotVariable(Expression arg1, Variable arg2){
		super(null);
		this.arg1 = arg1;
		this.arg2 = arg2.getName();
	}
	@Override
	public Object eval(Scope s) {
		if(EmbyRuntime.echo){
			emby.util.Print.println("Evaluating variable "+arg2+")");
		}
		Object o = arg1.eval(s);
		if(o instanceof OverloadDot){
			return ((OverloadDot) o).get_dot(arg1.getLine(),arg2);
		}
		else if (!(o instanceof EmbyObject)){
			//Java object detected!!!!
			if( arg2.equals("__dict__")){
				ArrayList<Object> properties = new ArrayList<Object>();
				Class<? extends Object> oClass = o.getClass();
				//properties.addAll(fieldsToString(oClass.getDeclaredFields()));
				//properties.addAll(methodsToString(oClass.getMethods()));
				properties.addAll(fieldsToString( (ReflectionHelp.getAllFields(oClass))));		
				properties.addAll(methodsToString( (ReflectionHelp.getAllMethods(oClass))));			
				return new EmbyList(properties);
			}
			return evalJavaObject(o, s);
		}
		else {

			throw new TypeError(arg1.getLine(), o+"("+o.getClass()+")" + " does not support the dot expression");
			//return null;
		}

	}

	public void setVar(Scope s, Object value){
		if(EmbyRuntime.echo){
			emby.util.Print.println("Assigning "+value+" to dot expression "+arg2);
		}
		Object var = arg1.eval(s);

		if(!(var instanceof OverloadDot)){
			throw new TypeError(getLine(),var.getClass()+" does not support assignment through dot expression");
		}
		else{
			((OverloadDot)var).set_dot(getLine(), arg2, value);
		}
	}

	public String getName(){
		return arg2;
	}

	public String toString(){
		return "{DotExpression: "+arg2+"}";
	}
	
	public void codify(IEmbyCodifier ec){
		ec.dot(arg1, arg2);
	}

	/**
	 * {@inheritDoc}
	 */
	public boolean isReadOnly(){
		if (arg2.startsWith("$"))
			return true;
		return false;
	}

	private Object evalJavaObject(Object o, Scope s){
		//Use reflection to look for proper method/fields in a java object.

		// How do you deal with a field and a method with the same name? Fields override Methods
		//Look for fields.
		Class<? extends Object> oClass = o.getClass();
		return ReflectionHelp.getFieldValueOrMethodWrapper(this.getLine(), oClass, arg2,o);
	}

	/**
	 * For use in __dict__
	 * Converts methods into easy-to-read strings
	 * Private is denoted by {Pv}, protected by {Po}
	 * @param m
	 * @return
	 */
	private ArrayList<String> methodsToString(ArrayList<Method> m){
		ArrayList<String> al = new ArrayList<String>();
		for(int i = 0; i<m.size(); i++){
			StringBuilder methodstr =new StringBuilder();
			if( Modifier.isPrivate(m.get(i).getModifiers())) { //if the method is private
				methodstr.append("{Pv}");
			}
			if( Modifier.isProtected(m.get(i).getModifiers())) { //if the method is protected
				methodstr.append("{Po}");
			}
			methodstr.append(m.get(i).getName()+"(");
			Class<?>[] params = m.get(i).getParameterTypes();
			for(int j = 0; j<params.length; j++){
				methodstr.append( params[j].getCanonicalName());
				if( j!=params.length-1){
					methodstr.append(",");
				}
			}
			methodstr.append(")");
			al.add(methodstr.toString());
		}
		return al;
	}

	/**
	 * For use in __dict__
	 * Converts fields into easy-to-read strings
	 * Private is denoted by {Pv}, protected by {Po}
	 * @param m
	 * @return
	 */
	private ArrayList<String> fieldsToString(ArrayList<Field> f){
		ArrayList<String> al = new ArrayList<String>();
		for(int i = 0; i<f.size(); i++){
			String cheese = "";
			if( Modifier.isPrivate(f.get(i).getModifiers())) { // private
				cheese = ("{Pv}");
			}
			if( Modifier.isProtected(f.get(i).getModifiers())) { //protected
				cheese = ("{Po}");
			}
			al.add(cheese + f.get(i).getName());
		}
		return al;
	}
}
